package com.puzzlebazar.client.puzzle.view;

import java.util.Iterator;


import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratorPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.puzzlebazar.client.main.view.ScreenSizeListener;
import com.puzzlebazar.client.puzzle.model.History;
import com.puzzlebazar.client.puzzle.model.MoveTimer;
import com.puzzlebazar.client.puzzle.model.PuzzleModel;
import com.puzzlebazar.client.puzzle.view.HistoryView.HistoryViewInfo;
import com.puzzlebazar.client.squaregrid.view.SGView;
import com.puzzlebazar.client.squaregrid.view.SGViewImpl;
import com.puzzlebazar.client.util.Executable;
import com.puzzlebazar.client.util.Observable;
import com.puzzlebazar.client.util.Observer;
import com.puzzlebazar.client.util.Tool;
import com.puzzlebazar.client.util.Vec2i;

/**
 * Top:    The toolbar
 * Middle: The PuzzleArea showing the puzzle itself
 */
public class ExtPuzzleView extends Composite implements Observer, ToolbarContainer, ScreenSizeListener, HasText {	

	private enum HistoryViewType {
		HIDDEN, VIEWABLE, EDITABLE

	}

	private PuzzleModel puzzleModel = null;
	private MoveTimer moveTimer = null;
	private History history = null;

	
	private SGViewImpl puzzleView = null;
	
	private static final int TIMER_WIDTH = 50;

	private static final int MIN_PUZZLE_HEIGHT = 300;
	private static final int MIN_PUZZLE_WIDTH = 500;

	private static final int TOOLBAR_HEIGHT = 34;
	private static final int MESSAGE_HEIGHT = 30;
	
	private final VerticalPanel mainPanel = new VerticalPanel();;
	private final Label timerLabel = new Label();
	private final HorizontalPanel topBar = new HorizontalPanel();
	private final HorizontalPanel toolbarContainer = new HorizontalPanel();;
	private final HorizontalPanel messageBar = new HorizontalPanel();
	private final Label messageBox = new Label();
	private final DecoratorPanel decoratedMessageBox = new DecoratorPanel();
	private final AbsolutePanel puzzlePanel = new AbsolutePanel();
	private HistoryView historyView = null;
	
	private HistoryViewType showHistory;
	private Timer textTimer;
	private boolean hasExternalText = false;
	
	
	/**
	 * This widget contains all the UI related to a puzzle, more or less
	 * directly. This includes the puzzle area (for the puzzle itself), the toolbar,
	 * and any extra information windows in the bottom pane. One of these windows
	 * contains messages.
	 */
	public ExtPuzzleView() {
		
		topBar.setWidth( "100%" );
		topBar.setHeight( TOOLBAR_HEIGHT + "px" );

		HTML spacer = new HTML("&nbsp;");
		spacer.setPixelSize( TIMER_WIDTH, TOOLBAR_HEIGHT );			
		
		toolbarContainer.setWidth( "100%" );
		toolbarContainer.setHeight( TOOLBAR_HEIGHT + "px" );
		toolbarContainer.setHorizontalAlignment( HasHorizontalAlignment.ALIGN_CENTER );
		
		timerLabel.setStylePrimaryName("timer");
		timerLabel.setPixelSize( TIMER_WIDTH, TOOLBAR_HEIGHT );		
		
		topBar.add(spacer);
		topBar.setCellWidth( spacer, TIMER_WIDTH + "px" );
		topBar.add(toolbarContainer);
		topBar.add(timerLabel);
		topBar.setCellWidth( timerLabel, TIMER_WIDTH + "px" );


		messageBox.setStylePrimaryName( "puzzleMessage" );
		
	    decoratedMessageBox.setWidget( messageBox );
	    decoratedMessageBox.setVisible( false );
	    
	    messageBar.setWidth( "100%" );
	    messageBar.setHeight( MESSAGE_HEIGHT + "px" );
	    messageBar.setVerticalAlignment( HasVerticalAlignment.ALIGN_TOP );
	    messageBar.setHorizontalAlignment( HasHorizontalAlignment.ALIGN_CENTER );
	    messageBar.add( decoratedMessageBox );
	    
		mainPanel.add( topBar );
		mainPanel.add( messageBar );
		mainPanel.add( puzzlePanel );
		showHistory = HistoryViewType.HIDDEN;
		
		textTimer = new Timer() {
			@Override
			public void run() {
				clearText();
			}
			
		};
		
		initWidget( mainPanel );
		
	}
	
	@Override
	public void addToolbar( Toolbar toolbar ) {
		toolbarContainer.add( toolbar );
	}

	@Override
	public void removeToolbar( Toolbar toolbar ) {
		toolbarContainer.remove( toolbar );
	}

	@Override
	public void clearToolbars() {
		toolbarContainer.clear();
	}

	@Override
	public void setAllToolbarVisible( boolean visible ) {
		for (Iterator<Widget> iterator = toolbarContainer.iterator(); iterator.hasNext();) {
			Widget widget = iterator.next();
			if( widget instanceof Toolbar )
				((Toolbar)widget).setVisible(visible);
		}
	}
	
	/**
	 * Adds a toolbar to the history toolbar container
	 * 
	 * @param toolbar The toolbar to add
	 */
	public void addHistoryToolbar(Toolbar toolbar) {
		if( showHistory != HistoryViewType.EDITABLE )
			return;
		((HistoryViewEditable)historyView).addToolbar(toolbar);	
	}

	/**
	 * Removes a toolbar from the history toolbar container
	 * 
	 * @param toolbar The toolbar to remove
	 */
	public void removeHistoryToolbar(Toolbar toolbar) {
		if( showHistory != HistoryViewType.EDITABLE )
			return;
		((HistoryViewEditable)historyView).removeToolbar(toolbar);	
	}

	/**
	 * Removes all historytoolbars from the toolbar container
	 */
	public void clearHistoryToolbars() {
		if( showHistory != HistoryViewType.EDITABLE )
			return;
		((HistoryViewEditable)historyView).clearToolbars();
	}

	/**
	 * Force all history toolbars to be visible or invisible. Doesn't apply to toolbars
	 * added after this call
	 *
	 * @param visible True to set all toolbars visible, false to set them invisible
	 */
	public void setAllHistoryToolbarVisible( boolean visible ) {
		if( showHistory != HistoryViewType.EDITABLE )
			return;
		((HistoryViewEditable)historyView).setAllToolbarVisible(visible);	
	}
	
	@Override
	protected void onLoad() {
		super.onLoad();
	}

	@Override
	public void screenSizeChanged( Vec2i screenSize ) {
		mainPanel.setPixelSize( screenSize.x, screenSize.y );
		
		
		Vec2i innerScreenSize = new Vec2i(screenSize);
		innerScreenSize.y -= topBar.getOffsetHeight();
		innerScreenSize.y -= messageBar.getOffsetHeight();
		if( showHistory != HistoryViewType.HIDDEN && historyView != null ) {
			historyView.screenSizeChanged( innerScreenSize );
			innerScreenSize.y -= historyView.getOffsetHeight();
		}
		
		updatePuzzlePanelSize( innerScreenSize );
	}

	/**
	 * Show an history view that cannot be edited.
	 * Should call refreshView() on the 
	 * main view some time after this.
	 */
	public void showHistory() {
		switch( showHistory ) {
			case VIEWABLE:
				return;
			case EDITABLE:
				hideHistory();
				break;
		}
		
		showHistory = HistoryViewType.VIEWABLE;
		if( history != null ) {
			historyView = new HistoryView( history, moveTimer, puzzleModel );			
			mainPanel.add( historyView );
		}
	}

	/**
	 * Show an history view that can be edited.
	 * Should call refreshView() on the 
	 * main view some time after this.
	 */
	public void showEditableHistory() {
		switch( showHistory ) {
			case EDITABLE:
				return;
			case VIEWABLE:
				hideHistory();
				break;
		}
		
		showHistory = HistoryViewType.EDITABLE;
		if( history != null ) {
			historyView = new HistoryViewEditable( history, moveTimer, puzzleModel );			
			mainPanel.add( historyView );
		}
	}

	/**
	 * Hide any history view.
	 * Should call refreshView() on the 
	 * main view some time after this.
	 */
	public void hideHistory() {
		if( showHistory == HistoryViewType.HIDDEN || historyView == null )
			return;
		
		showHistory = HistoryViewType.HIDDEN;
		mainPanel.remove( historyView );
		historyView.release();
		historyView = null;
	}
	
	
	/**
	 * Removes the puzzle and other resources
	 */
	public void removeAll() {
		timerLabel.setText("");
		clearToolbars();
		clearHistoryToolbars();
		clearText();
		
		if( puzzleView != null )
			puzzlePanel.remove( puzzleView );		
		puzzleView = null;
		if( moveTimer != null )
			moveTimer.deleteObserver( this );
		moveTimer = null;
		if( puzzleModel != null )
			puzzleModel.deleteObserver( this );
		puzzleModel = null;

		hideHistory();
		
		history = null;
	}
	
	/**
	 * Adds the puzzle, no waiting screen is displayed
	 * 
	 * @param puzzleView The view that displays the puzzle
	 * @throws Exception
	 */
	public void setPuzzleView( SGViewImpl puzzleView ) throws Exception {
		if( this.puzzleView != null ) 
			throw new Exception( "Puzzle widget can only be set once" );
		
		this.puzzleView = puzzleView;
		puzzlePanel.add( puzzleView );
		resizePuzzleView( new Vec2i( puzzlePanel.getOffsetWidth(), puzzlePanel.getOffsetHeight()) );
		puzzleModel = puzzleView.getPuzzleModel();
		puzzleModel.addObserver(this);
	}

	
	/**
	 * @return The associated puzzle view
	 */
	public SGView getPuzzleView() {
		return puzzleView;
	}

	
	/**
	 * Sets the move timer associated with this view. 
	 * 
	 * @param moveTimer The move timer to associate with this view
	 */
	public void setMoveTimer(MoveTimer moveTimer) {
		if( this.moveTimer != null )
			this.moveTimer.deleteObserver( this );
		this.moveTimer = moveTimer;
		moveTimer.addObserver(this);
	}
	
	/**
	 * @param history The history to associate with this view
	 * Should call refreshView() on the 
	 * main view some time after this.
	 */
	public void setHistory(History history) {
		this.history = history;
		if( showHistory != HistoryViewType.HIDDEN ) {
			hideHistory();
			switch( showHistory ) {
				case EDITABLE:
					showEditableHistory();
					break;
				case VIEWABLE:
					showHistory();
					break;
			}
		}
	}
	
	/**
	 * Called whenever the size of the puzzle panel need to be modified.
	 * 
	 * @param size The new size to use for the puzzle panel
	 */
	public void updatePuzzlePanelSize( Vec2i size ) {
		if( size.x < MIN_PUZZLE_WIDTH ) size.x = MIN_PUZZLE_WIDTH;
		if( size.y < MIN_PUZZLE_HEIGHT ) size.y = MIN_PUZZLE_HEIGHT;
		puzzlePanel.setPixelSize( size.x, size.y );
		
		if( puzzleView != null )
			resizePuzzleView(size);
	}

	/**
	 * Make sure the puzzle view is correctly positioned and sized
	 * 
	 * @param size The size to be occupied by the puzzleview
	 */
	private void resizePuzzleView(Vec2i size) {
		puzzlePanel.setWidgetPosition( puzzleView, 0, 0);
		try {
			puzzleView.setOuterSize(size);
		} catch (Exception exception) {
			exception.printStackTrace();
		}
	}
	
	/**
	 * Controls the visibility of the embedded puzzle
	 * 
	 * @param visible True to set the embedded puzzle visible, false to make it invisible
	 */
	public void setPuzzleVisible( boolean visible ) {
		puzzleView.setVisible( visible );
	}

	
	@Override
	public void release() {
		removeAll();
	}

	@Override
	public void update(Observable observable, Object arg) {
		if( observable == moveTimer )
			timerLabel.setText( moveTimer.getText() );
		else if( observable == puzzleModel ) {
			if( !hasExternalText )
				displayMessage( puzzleModel.getMessage() );
		}
		else
			assert( false );
	}

	/**
	 * Display the passed text in the message box
	 * 
	 * @param string The message to display
	 */
	private void displayMessage(String string) {
		if( string == null || string.length() == 0 )
			decoratedMessageBox.setVisible(false);
		else {	
			messageBox.setText( string );
			decoratedMessageBox.setVisible( true );
		}
	}
	
	public void setText(String string) {
		if( string == null || string.length() == 0 )
			clearText();
		displayMessage( string );
		textTimer.cancel();
		hasExternalText = true;
	}

	/**
	 * Display a message for a specific amout of time
	 * 
	 * @param string The message to display
	 * @param delay The duration during which to display the message
	 */
	public void setTimedText(String string, int delay) {
		setText( string );
		textTimer.schedule( delay );
	}
	
	public String getText() {
		return messageBox.getText();
	}
	
	/**
	 * Clear any message displayed at the top of the puzzle
	 */
	public void clearText() {
		textTimer.cancel();
		messageBox.setText( "" );
		decoratedMessageBox.setVisible( false );
		hasExternalText = false;
		if( puzzleModel != null )
			displayMessage( puzzleModel.getMessage() );
	}

	public void addTempButton(String string, final Executable executable) {
		setAllToolbarVisible(false);

		final Toolbar tempToolbar = new Toolbar();
		addToolbar(tempToolbar);
		final Tool continueButton = new Tool(string) {
			@Override
			public void execute() {
				removeToolbar(tempToolbar);
				setAllToolbarVisible(true);
				executable.execute();
			}
		};

		tempToolbar.add(continueButton);
		
	}

	/**
	 * @return The attached history view information
	 */
	public HistoryViewInfo getHistoryViewInfo() {
		return historyView.getHistoryViewInfo();
	}

	
	
}
